// -*- mode: c++; compile-command: "cd ..; make bin/24.raytrace" -*-
/// \file 24.raytrace.cc
/// \brief Raytracing example.

#include <SDL/SDL.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <ciso646>
#include <cassert>
#include <cmath>
#include <cstring>
#include <iostream>
#include <vector>
#include <cgmath/cgmath.hh>
#include "raytracer.hh"
#include "lambert_shader.hh"
#include "texture_shader.hh"
#include "phong_shader.hh"
#include "mirror_shader.hh"
#include "glass_shader.hh"
#include "point_light.hh"
#include "sphere_geometry.hh"
#include "plane_geometry.hh"
#include "mesh.hh"
#include "ply_reader.hh"
#include "mesh_geometry.hh"
#include "cone_geometry.hh"

using namespace std;
using namespace cgmath;

static void init_window      (unsigned width, unsigned height);
static void event_loop       (void);
static void handle_event     (SDL_Event& event);
static void handle_keyup     (SDL_Event& event);
static void handle_keydown   (SDL_Event& event);
static void handle_mouse_button_down (SDL_Event& event);
static void handle_mouse_motion      (SDL_Event& event);
static void idle             (void);
static void init_scene_1     (void);
static void init_scene_3     (void);
static void init_scene_4     (void);
static void init_scene_5     (void);
static void init_scene_7     (void);
static void init_scene_8     (void);
static void init_scene_9     (void);
static void init_scene_11    (void);
static void init_scene_12    (void);
static void update_scene     (void);
static void render_scene     (void);

static bool exit_event_loop = false;

raytracer*       tracer;

char             cube_ply[] = "obj/cube.ply";

// sphere_geometry* sphere1_geometry;
// lambert_shader*  sphere1_shader;
// object*          sphere1;

// sphere_geometry* sphere2_geometry;
// lambert_shader*  sphere2_shader;
// object*          sphere2;

// plane_geometry*  plane1_geometry;
// lambert_shader*  plane1_shader;
// object*          plane1;

//char*            mesh1_file; 
// mesh_geometry*   mesh1_geometry;
// lambert_shader*  mesh1_shader;
// object*          mesh1;

//char*            mesh2_file;
// mesh_geometry*   mesh2_geometry;
// lambert_shader*  mesh2_shader;
// object*          mesh2;

point_light*     light1;
point_light*     light2;

matrix_4d        projection_matrix;
matrix_4d        projection_view_projection;
matrix_4d        view_matrix;
SDL_Surface*     surf;
unsigned         scanline = 0;

bool use_path_tracer = false;
bool use_russian_path_tracer = false;
unsigned bounce_depth = 1;
bool use_dof = false;
double focal_distance = 12.0;


int main (int argc, char* argv[])
{
  int c;
  int scene = 1;
  while ((c = getopt (argc, argv, "s:")) != -1)
    {
      switch (c)
        {
        case 's':
          scene = atoi (optarg);
          break;
          
        default:
          exit (1);
        }
    }
  
  init_window (640, 480);

  switch (scene)
    {
    case 1:
    case 2:
      init_scene_1 ();
      break;
      
    case 3:
      init_scene_3 ();
      break;

    case 4:
      init_scene_4 ();
      break;

    case 5:
      init_scene_5 ();
      break;

    case 7:
      init_scene_7 ();
      break;

    case 8:
      init_scene_8 ();
      break;

    case 9:
      init_scene_9 ();
      break;
      
    case 11:
      init_scene_11 ();
      break;
      
    case 12:
      use_dof = true;
      init_scene_12 ();
      break;
      
    default:
      exit (1);
    }
  
  event_loop ();
  return 0;
}

/// Initialize a window for single buffered OpenGL rendering.
void init_window (unsigned width, unsigned height)
{
  // Initialize SDL and OpenGL with 8 bits for each RGBA. No double buffering.
  SDL_Init (SDL_INIT_VIDEO);
  atexit (SDL_Quit);
  SDL_GL_SetAttribute (SDL_GL_RED_SIZE,     8);
  SDL_GL_SetAttribute (SDL_GL_GREEN_SIZE,   8);
  SDL_GL_SetAttribute (SDL_GL_BLUE_SIZE,    8);
  SDL_GL_SetAttribute (SDL_GL_ALPHA_SIZE,   8);
  SDL_GL_SetAttribute (SDL_GL_DOUBLEBUFFER, 1);
  SDL_Surface* surface;
  surface = SDL_SetVideoMode (width, height, 32, SDL_OPENGL);
  assert (surface);
}

/// Handle events. Do idle processing while there are no events.
void event_loop (void)
{
  SDL_Event event;
  while (not exit_event_loop)
    if (SDL_PollEvent (&event))
      handle_event (event);
    else
      idle ();
}

/// Handle a given event. Dispatches by type to other event handlers.
void handle_event (SDL_Event& event)
{
  switch (event.type) 
    {
    case SDL_KEYDOWN:         handle_keydown (event); break;
    case SDL_KEYUP:           handle_keyup   (event); break;
    case SDL_MOUSEMOTION:     handle_mouse_motion (event); break;
    case SDL_MOUSEBUTTONDOWN: handle_mouse_button_down (event); break;
    }
}

/// Handle pressing a key on the keyboard. Set quit on Esc pressed.
void handle_keydown (SDL_Event& event)
{
  switch (event.key.keysym.sym)
    {
    case SDLK_ESCAPE:
      exit_event_loop = true;
      break;
      
    case SDLK_a:
      focal_distance--;
      tracer->focal_distance = focal_distance;
      tracer->clear_screen (surf);
      cout << "focal_distance: " << focal_distance << "\n";
      scanline = 0;
      break;
      
    case SDLK_s:
      focal_distance++;
      tracer->focal_distance = focal_distance;
      tracer->clear_screen (surf);
      cout << "focal_distance: " << focal_distance << "\n";
      scanline = 0;
      break;
      
    case SDLK_j:
      tracer->paths /= 2;
      if (tracer->paths < 1) tracer->paths = 1;
      tracer->clear_screen (surf);
      cout << "paths: " << tracer->paths << "\n";
      scanline = 0;
      break;

    case SDLK_k:
      tracer->paths *= 2;
      tracer->clear_screen (surf);
      cout << "paths: " << tracer->paths << "\n";
      scanline = 0;
      break;

    case SDLK_u:
      tracer->branching_factor /= 2;
      if (tracer->branching_factor < 1) tracer->branching_factor = 1;
      tracer->clear_screen (surf);
      cout << "branching factor: " << tracer->branching_factor << "\n";
      scanline = 0;
      break;

    case SDLK_i:
      tracer->branching_factor *= 2;
      tracer->clear_screen (surf);
      cout << "branching factor: " << tracer->branching_factor << "\n";
      scanline = 0;
      break;
      
    default: ;
    }
}

void handle_keyup (SDL_Event& event)
{}

void handle_mouse_button_down (SDL_Event& event)
{}

void handle_mouse_motion (SDL_Event& event)
{}

static void init_scene_1 ()
{
  tracer  = new raytracer;

  sphere_geometry* sphere1_geometry = new sphere_geometry (vec(0.0, 0.0, 0.0), 1.0);
  lambert_shader* sphere1_shader = new lambert_shader (vec(0.6,0.6,0.05));
  object* sphere1 = new object (translate(vec(-1.0, 0.0, 0.0)),
                                sphere1_geometry, 
                                sphere1_shader);
  
  plane_geometry* plane1_geometry = new plane_geometry (plane_3d(vec(0.0, 1.0, 0.0), 1.0));
  lambert_shader* plane1_shader = new lambert_shader (vec(0.4,0.4,0.4));
  object* plane1 = new object(matrix_4d::identity(), 
                              plane1_geometry,
                              plane1_shader);

  mesh_geometry* mesh1_geometry = new mesh_geometry (cube_ply);
  lambert_shader* mesh1_shader = new lambert_shader (vec(0.4, 0.0, 0.0));
  object* mesh1 = new object (translate(vec(0.0, -0.5, -3.0)) *
                              rotate_4(0.3, 0.0, 1.0, 0.0) *
                              rotate_4(0.9, 1.0, 0.0, 0.0) *
                              scale(0.6, 0.6, 0.6),
                              mesh1_geometry,
                              mesh1_shader);
  
  mesh_geometry* mesh2_geometry = new mesh_geometry (cube_ply);
  lambert_shader* mesh2_shader = new lambert_shader (vec(0.0, 0.2, 0.9));
  object* mesh2 = new object (translate(vec(1.5, 0.2, 1.5)) *
                              rotate_4(0.3, 0.0, 1.0, 0.0) *
                              rotate_4(0.4, 1.0, 0.0, 0.0) *
                              scale(0.5, 0.5, 0.5),
                              mesh2_geometry,
                              mesh2_shader);
  
  light1 = new point_light (vec(3.0, 3.0, 3.0),  vec(20.0, 20.0, 20.0));
  light2 = new point_light (vec(-3.0, 3.0, -3.0), vec( 3.0, 3.0, 10.0));
  
  tracer->objects.push_back (sphere1);
  // tracer->objects.push_back (sphere2); //debug
  tracer->objects.push_back (plane1);
  tracer->objects.push_back (mesh1);
  tracer->objects.push_back (mesh2);
  tracer->lights.push_back  (light1);
  tracer->lights.push_back  (light2);
  
  surf = SDL_CreateRGBSurface (SDL_SWSURFACE,
			       640, 480, 32,
			       0x000000ff,
			       0x0000ff00,
			       0x00ff0000,
			       0xff000000);
  
  // Setup viewing transformations as in OpenGL.
  matrix_4d view = lookat(vec(0.0, 1.0, 6.0),
			  vec(0.0, 0.0, 0.0),
			  vec(0.0, 1.0, 0.0));
  matrix_4d P  = perspective (M_PI/4.0, 640.0/480.0, 0.5, 10000.0);
  matrix_4d VP = viewport<double>(0, 0, 640, 480);
  projection_matrix = VP * P * view;
  projection_view_projection = VP * P;
  view_matrix = view;
}

static void init_scene_3 ()
{
  use_path_tracer = true;
  tracer  = new raytracer;

  sphere_geometry* sphere1_geometry = new sphere_geometry (vec(0.0, 0.0, 0.0), 1.0);
  lambert_shader* sphere1_shader = new lambert_shader (vec(0.6,0.6,0.05));
  object* sphere1 = new object (translate(vec(-1.0, 0.0, 0.0)),
                                sphere1_geometry, 
                                sphere1_shader);
  
  plane_geometry* plane1_geometry = new plane_geometry (plane_3d(vec(0.0, 1.0, 0.0), 1.0));
  lambert_shader* plane1_shader = new lambert_shader (vec(0.4,0.4,0.4));
  object* plane1 = new object(matrix_4d::identity(), 
                              plane1_geometry,
                              plane1_shader);

  mesh_geometry* mesh1_geometry = new mesh_geometry (cube_ply);
  lambert_shader* mesh1_shader = new lambert_shader (vec(0.4, 0.0, 0.0));
  object* mesh1 = new object (translate(vec(0.0, -0.5, -3.0)) *
                              rotate_4(0.3, 0.0, 1.0, 0.0) *
                              rotate_4(0.9, 1.0, 0.0, 0.0) *
                              scale(0.6, 0.6, 0.6),
                              mesh1_geometry,
                              mesh1_shader);
  
  mesh_geometry* mesh2_geometry = new mesh_geometry (cube_ply);
  lambert_shader* mesh2_shader = new lambert_shader (vec(0.0, 0.2, 0.9));
  object* mesh2 = new object (translate(vec(1.5, 0.2, 1.5)) *
                              rotate_4(0.3, 0.0, 1.0, 0.0) *
                              rotate_4(0.4, 1.0, 0.0, 0.0) *
                              scale(0.5, 0.5, 0.5),
                              mesh2_geometry,
                              mesh2_shader);
  
  light1 = new point_light (vec(3.0, 3.0, 3.0),  vec(20.0, 20.0, 20.0));
  light2 = new point_light (vec(-3.0, 3.0, -3.0), vec( 3.0, 3.0, 10.0));
  
  tracer->objects.push_back (sphere1);
  // tracer->objects.push_back (sphere2); //debug
  tracer->objects.push_back (plane1);
  tracer->objects.push_back (mesh1);
  tracer->objects.push_back (mesh2);
  tracer->lights.push_back  (light1);
  tracer->lights.push_back  (light2);
  
  surf = SDL_CreateRGBSurface (SDL_SWSURFACE,
			       640, 480, 32,
			       0x000000ff,
			       0x0000ff00,
			       0x00ff0000,
			       0xff000000);
  
  // Setup viewing transformations as in OpenGL.
  matrix_4d view = lookat(vec(0.0, 1.0, 6.0),
			  vec(0.0, 0.0, 0.0),
			  vec(0.0, 1.0, 0.0));
  matrix_4d P  = perspective (M_PI/4.0, 640.0/480.0, 0.5, 10000.0);
  matrix_4d VP = viewport<double>(0, 0, 640, 480);
  projection_matrix = VP * P * view;
  projection_view_projection = VP * P;
  view_matrix = view;
}


static void init_scene_4 ()
{
  tracer  = new raytracer;

  sphere_geometry* sphere1_geometry = new sphere_geometry (vec(0.0, 0.0, 0.0), 1.0);
  lambert_shader* sphere1_shader = new lambert_shader (vec(0.6,0.6,0.05));
  object* sphere1 = new object (translate(vec(2.1, 0.0, 0.0)),
                                sphere1_geometry, 
                                sphere1_shader);

  cone_geometry* cone1_geometry = new cone_geometry (2.0, 4.0);
  lambert_shader* cone1_shader = new lambert_shader (vec(0.2,0.6,0.05));
  object* cone1 = new object (translate(vec(-0.2, 1.5, -2.5)) *
			      rotate_4(0.3, 0.0, 1.0, 0.0) *
                              rotate_4(0.9, 1.5, 0.0, 0.0) *
                              scale(0.6, 0.6, 0.6),
			      cone1_geometry, 
			      cone1_shader);

  
  cone_geometry* cone2_geometry = new cone_geometry (3.4, 2.0);
  lambert_shader* cone2_shader = new lambert_shader (vec(1.0,0.1,0.05));
  object* cone2 = new object (translate(vec(-2.5, 0.7, -2.0)) *
			      rotate_4(-0.4, 1.5, 1.0, 0.0) *
                              scale(0.4, 0.4, 0.2),
			      cone2_geometry, 
			      cone2_shader);
  
  plane_geometry* plane1_geometry = new plane_geometry (plane_3d(vec(0.0, 1.0, 0.0), 1.0));
  lambert_shader* plane1_shader = new lambert_shader (vec(0.4,0.4,0.4));
  object* plane1 = new object(matrix_4d::identity(), 
                              plane1_geometry,
                              plane1_shader);

  light1 = new point_light (vec(3.0, 3.0, 3.0),  vec(20.0, 20.0, 20.0));
  light2 = new point_light (vec(-3.0, 3.0, -3.0), vec( 3.0, 3.0, 10.0));
  
  tracer->objects.push_back (sphere1);
  tracer->objects.push_back (cone1);
  tracer->objects.push_back (cone2);
  tracer->objects.push_back (plane1);
  tracer->lights.push_back  (light1);
  tracer->lights.push_back  (light2);
  
  surf = SDL_CreateRGBSurface (SDL_SWSURFACE,
			       640, 480, 32,
			       0x000000ff,
			       0x0000ff00,
			       0x00ff0000,
			       0xff000000);
  
  // Setup viewing transformations as in OpenGL.
  matrix_4d view = lookat(vec(2.0, 1.0, 6.0),
			  vec(0.0, 0.0, 0.0),
			  vec(0.0, 1.0, 0.0));
  matrix_4d P  = perspective (M_PI/4.0, 640.0/480.0, 0.5, 10000.0);
  matrix_4d VP = viewport<double>(0, 0, 640, 480);
  projection_matrix = VP * P * view;
  projection_view_projection = VP * P;
  view_matrix = view;
}

static void init_scene_5 ()
{
  tracer  = new raytracer;

  sphere_geometry* sphere1_geometry = new sphere_geometry (vec(0.0, 0.0, 0.0), 1.0);
  texture_shader* sphere1_shader = new texture_shader ("tex/chess.jpg");
  object* sphere1 = new object (translate(vec(-1.0, 0.0, 0.0)),
                                sphere1_geometry, 
                                sphere1_shader);

  sphere_geometry* sphere2_geometry = new sphere_geometry (vec(0.0, 0.0, 0.0), 1.0);
  texture_shader* sphere2_shader = new texture_shader ("tex/marble.jpg");
  object* sphere2 = new object (translate(vec(1.5, 0.0, 0.0)),
                                sphere2_geometry, 
                                sphere2_shader);
  
  plane_geometry* plane1_geometry = new plane_geometry (plane_3d(vec(0.0, 1.0, 0.0), 1.0));
  lambert_shader* plane1_shader = new lambert_shader (vec(0.4,0.4,0.4));
  object* plane1 = new object(matrix_4d::identity(), 
                              plane1_geometry,
                              plane1_shader);

  light1 = new point_light (vec(3.0, 3.0, 3.0),  vec(20.0, 20.0, 20.0));
  light2 = new point_light (vec(-3.0, 3.0, -3.0), vec( 3.0, 3.0, 10.0));
  
  tracer->objects.push_back (sphere1);
  tracer->objects.push_back (sphere2);
  tracer->objects.push_back (plane1);
  tracer->lights.push_back  (light1);
  tracer->lights.push_back  (light2);
  
  surf = SDL_CreateRGBSurface (SDL_SWSURFACE,
			       640, 480, 32,
			       0x000000ff,
			       0x0000ff00,
			       0x00ff0000,
			       0xff000000);
  
  // Setup viewing transformations as in OpenGL.
  matrix_4d view = lookat(vec(0.0, 1.0, 6.0),
			  vec(0.0, 0.0, 0.0),
			  vec(0.0, 1.0, 0.0));
  matrix_4d P  = perspective (M_PI/4.0, 640.0/480.0, 0.5, 10000.0);
  matrix_4d VP = viewport<double>(0, 0, 640, 480);
  projection_matrix = VP * P * view;
  projection_view_projection = VP * P;
  view_matrix = view;
}


static void init_scene_7 ()
{
  use_russian_path_tracer = true;
  tracer  = new raytracer;

  sphere_geometry* sphere1_geometry = new sphere_geometry (vec(0.0, 0.0, 0.0), 1.0);
  mirror_shader* sphere1_shader = new mirror_shader;
  object* sphere1 = new object (translate(vec(-1.0, 0.0, 0.0)),
                                sphere1_geometry, 
                                sphere1_shader);
  
  plane_geometry* plane1_geometry = new plane_geometry (plane_3d(vec(0.0, 1.0, 0.0), 1.0));
  lambert_shader* plane1_shader = new lambert_shader (vec(0.4,0.4,0.4));
  object* plane1 = new object(matrix_4d::identity(), 
                              plane1_geometry,
                              plane1_shader);

  mesh_geometry* mesh1_geometry = new mesh_geometry (cube_ply);
  lambert_shader* mesh1_shader = new lambert_shader (vec(0.4, 0.0, 0.0));
  object* mesh1 = new object (translate(vec(0.0, -0.5, -3.0)) *
                              rotate_4(0.3, 0.0, 1.0, 0.0) *
                              rotate_4(0.9, 1.0, 0.0, 0.0) *
                              scale(0.6, 0.6, 0.6),
                              mesh1_geometry,
                              mesh1_shader);
  
  mesh_geometry* mesh2_geometry = new mesh_geometry (cube_ply);
  glass_shader* mesh2_shader = new glass_shader (1.05);
  object* mesh2 = new object (translate(vec(1.5, 0.2, 1.5)) *
                              rotate_4(0.3, 0.0, 1.0, 0.0) *
                              rotate_4(0.4, 1.0, 0.0, 0.0) *
                              scale(0.5, 0.5, 0.5),
                              mesh2_geometry,
                              mesh2_shader);

  mesh_geometry* mesh3_geometry = new mesh_geometry (cube_ply);
  lambert_shader* mesh3_shader = new lambert_shader (vec (0.0, 0.4, 0.8));
  object* mesh3 = new object (translate(vec(1.5, 0.2, -1.5)) *
                              rotate_4(0.3, 0.0, 1.0, 0.0) *
                              rotate_4(0.4, 1.0, 0.0, 0.0) *
                              scale(0.5, 0.5, 0.5),
                              mesh3_geometry,
                              mesh3_shader);

  mesh_geometry* mesh4_geometry = new mesh_geometry (cube_ply);
  lambert_shader* mesh4_shader = new lambert_shader (vec (0.3, 0.4, 0.1));
  object* mesh4 = new object (translate(vec(2.5, 1.2, -2.5)) *
                              rotate_4(0.3, 0.0, 1.0, 0.0) *
                              rotate_4(0.4, 1.0, 0.0, 0.0) *
                              scale(0.5, 0.5, 0.8),
                              mesh4_geometry,
                              mesh4_shader);
  
  light1 = new point_light (vec(3.0, 3.0, 3.0),  vec(20.0, 20.0, 20.0));
  light2 = new point_light (vec(-3.0, 3.0, -3.0), vec( 3.0, 3.0, 10.0));
  
  tracer->objects.push_back (sphere1);
  // tracer->objects.push_back (sphere2); //debug
  tracer->objects.push_back (plane1);
  tracer->objects.push_back (mesh1);
  tracer->objects.push_back (mesh2);
  tracer->objects.push_back (mesh3);
  tracer->objects.push_back (mesh4);
  tracer->lights.push_back  (light1);
  tracer->lights.push_back  (light2);
  
  surf = SDL_CreateRGBSurface (SDL_SWSURFACE,
			       640, 480, 32,
			       0x000000ff,
			       0x0000ff00,
			       0x00ff0000,
			       0xff000000);
  
  // Setup viewing transformations as in OpenGL.
  matrix_4d view = lookat(vec(0.0, 1.0, 6.0),
			  vec(0.0, 0.0, 0.0),
			  vec(0.0, 1.0, 0.0));
  matrix_4d P  = perspective (M_PI/4.0, 640.0/480.0, 0.5, 10000.0);
  matrix_4d VP = viewport<double>(0, 0, 640, 480);
  projection_matrix = VP * P * view;
  projection_view_projection = VP * P;
  view_matrix = view;
}

static void init_scene_8 ()
{
  use_russian_path_tracer = true;
  tracer  = new raytracer;
  double ns = 20.0;

  sphere_geometry* sphere1_geometry = new sphere_geometry (vec(0.0, 0.0, 0.0), 1.0);
  phong_shader* sphere1_shader = new phong_shader (vec(0.2, 0.8, 0.8), ns, false);
  object* sphere1 = new object (translate(vec(-1.0, 0.0, 0.0)),
                                sphere1_geometry, 
                                sphere1_shader);

  sphere_geometry* sphere2_geometry = new sphere_geometry (vec(0.0, 0.0, 0.0), 1.0);
  phong_shader* sphere2_shader = new phong_shader (vec(0.2, 0.0, 0.8), ns, false);
  object* sphere2 = new object (translate(vec(1.5, 0.0, 0.0)),
                                sphere2_geometry, 
                                sphere2_shader);
  
  plane_geometry* plane1_geometry = new plane_geometry (plane_3d(vec(0.0, 1.0, 0.0), 1.0));
  phong_shader* plane1_shader = new phong_shader(vec(0.4, 0.4, 0.4), ns, false);
  object* plane1 = new object(matrix_4d::identity(), 
                              plane1_geometry,
                              plane1_shader);
  
  light1 = new point_light (vec(3.0, 3.0, 3.0),  vec(20.0, 20.0, 20.0));
  light2 = new point_light (vec(-3.0, 3.0, -3.0), vec( 3.0, 3.0, 10.0));
  
  tracer->objects.push_back (sphere1);
  tracer->objects.push_back (sphere2); //debug
  tracer->objects.push_back (plane1);
  tracer->lights.push_back  (light1);
  tracer->lights.push_back  (light2);
  
  surf = SDL_CreateRGBSurface (SDL_SWSURFACE,
			       640, 480, 32,
			       0x000000ff,
			       0x0000ff00,
			       0x00ff0000,
			       0xff000000);
  
  // Setup viewing transformations as in OpenGL.
  matrix_4d view = lookat(vec(0.0, 1.0, 6.0),
			  vec(0.0, 0.0, 0.0),
			  vec(0.0, 1.0, 0.0));
  matrix_4d P  = perspective (M_PI/4.0, 640.0/480.0, 0.5, 10000.0);
  matrix_4d VP = viewport<double>(0, 0, 640, 480);
  projection_matrix = VP * P * view;
  projection_view_projection = VP * P;
  view_matrix = view;
}

static void init_scene_9 ()
{
  use_russian_path_tracer = true;
  tracer  = new raytracer;
  double ns = 20.0;

  sphere_geometry* sphere1_geometry = new sphere_geometry (vec(0.0, 0.0, 0.0), 1.0);
  phong_shader* sphere1_shader = new phong_shader (vec(0.2, 0.8, 0.8), ns, true);
  object* sphere1 = new object (translate(vec(-1.0, 0.0, 0.0)),
                                sphere1_geometry, 
                                sphere1_shader);

  sphere_geometry* sphere2_geometry = new sphere_geometry (vec(0.0, 0.0, 0.0), 1.0);
  phong_shader* sphere2_shader = new phong_shader (vec(0.2, 0.0, 0.8), ns, true);
  object* sphere2 = new object (translate(vec(1.5, 0.0, 0.0)),
                                sphere2_geometry, 
                                sphere2_shader);
  
  plane_geometry* plane1_geometry = new plane_geometry (plane_3d(vec(0.0, 1.0, 0.0), 1.0));
  phong_shader* plane1_shader = new phong_shader(vec(0.4, 0.4, 0.4), ns, true);
  object* plane1 = new object(matrix_4d::identity(), 
                              plane1_geometry,
                              plane1_shader);

  light1 = new point_light (vec(3.0, 3.0, 3.0),  vec(20.0, 20.0, 20.0));
  light2 = new point_light (vec(-3.0, 3.0, -3.0), vec( 3.0, 3.0, 10.0));
  
  tracer->objects.push_back (sphere1);
  tracer->objects.push_back (sphere2); //debug
  tracer->objects.push_back (plane1);
  tracer->lights.push_back  (light1);
  tracer->lights.push_back  (light2);
  
  surf = SDL_CreateRGBSurface (SDL_SWSURFACE,
			       640, 480, 32,
			       0x000000ff,
			       0x0000ff00,
			       0x00ff0000,
			       0xff000000);
  
  // Setup viewing transformations as in OpenGL.
  matrix_4d view = lookat(vec(0.0, 1.0, 6.0),
			  vec(0.0, 0.0, 0.0),
			  vec(0.0, 1.0, 0.0));
  matrix_4d P  = perspective (M_PI/4.0, 640.0/480.0, 0.5, 10000.0);
  matrix_4d VP = viewport<double>(0, 0, 640, 480);
  projection_matrix = VP * P * view;
  projection_view_projection = VP * P;
  view_matrix = view;
}

static void init_scene_11 ()
{
  use_russian_path_tracer = true;
  tracer  = new raytracer;

  sphere_geometry* sphere1_geometry = new sphere_geometry (vec(0.0, 0.0, 0.0), 1.0);
  lambert_shader* sphere1_shader = new lambert_shader (vec(0.6,0.6,0.05));
  object* sphere1 = new object (translate(vec(-1.0, 0.0, 0.0)),
                                sphere1_geometry, 
                                sphere1_shader);
  
  plane_geometry* plane1_geometry = new plane_geometry (plane_3d(vec(0.0, 1.0, 0.0), 1.0));
  lambert_shader* plane1_shader = new lambert_shader (vec(0.4,0.4,0.4));
  object* plane1 = new object(matrix_4d::identity(), 
                              plane1_geometry,
                              plane1_shader);

  mesh_geometry* mesh1_geometry = new mesh_geometry (cube_ply);
  lambert_shader* mesh1_shader = new lambert_shader (vec(0.4, 0.0, 0.0));
  object* mesh1 = new object (translate(vec(0.0, -0.5, -3.0)) *
                              rotate_4(0.3, 0.0, 1.0, 0.0) *
                              rotate_4(0.9, 1.0, 0.0, 0.0) *
                              scale(0.6, 0.6, 0.6),
                              mesh1_geometry,
                              mesh1_shader);
  
  mesh_geometry* mesh2_geometry = new mesh_geometry (cube_ply);
  lambert_shader* mesh2_shader = new lambert_shader (vec(0.0, 0.2, 0.9));
  object* mesh2 = new object (translate(vec(1.5, 0.2, 1.5)) *
                              rotate_4(0.3, 0.0, 1.0, 0.0) *
                              rotate_4(0.4, 1.0, 0.0, 0.0) *
                              scale(0.5, 0.5, 0.5),
                              mesh2_geometry,
                              mesh2_shader);
  
  light1 = new point_light (vec(3.0, 3.0, 3.0),  vec(20.0, 20.0, 20.0));
  light2 = new point_light (vec(-3.0, 3.0, -3.0), vec( 3.0, 3.0, 10.0));
  
  tracer->objects.push_back (sphere1);
  // tracer->objects.push_back (sphere2); //debug
  tracer->objects.push_back (plane1);
  tracer->objects.push_back (mesh1);
  tracer->objects.push_back (mesh2);
  tracer->lights.push_back  (light1);
  tracer->lights.push_back  (light2);
  
  surf = SDL_CreateRGBSurface (SDL_SWSURFACE,
			       640, 480, 32,
			       0x000000ff,
			       0x0000ff00,
			       0x00ff0000,
			       0xff000000);
  
  // Setup viewing transformations as in OpenGL.
  matrix_4d view = lookat(vec(0.0, 1.0, 6.0),
			  vec(0.0, 0.0, 0.0),
			  vec(0.0, 1.0, 0.0));
  matrix_4d P  = perspective (M_PI/4.0, 640.0/480.0, 0.5, 10000.0);
  matrix_4d VP = viewport<double>(0, 0, 640, 480);
  projection_matrix = VP * P * view;
  projection_view_projection = VP * P;
  view_matrix = view;
}


static void init_scene_12 ()
{
  tracer = new raytracer (focal_distance);
  
  for (int i = 0; i < 10; ++i)
    {
      sphere_geometry* sphere1_geometry = new sphere_geometry (vec(0.0, 0.0, 0.0), 1.0);
      lambert_shader* sphere1_shader = new lambert_shader (vec(0.6,0.6,0.05));
      object* sphere1 = new object (rotate_4(M_PI / 4.0, 0.0, 1.0, 0.0) * 
                                    translate(vec(4.0, 0.0, 2.0)) *
                                    translate(vec(0.0, 0.0, -(double)(i * 2.5))),
                                    sphere1_geometry, 
                                    sphere1_shader);
      tracer->objects.push_back (sphere1);
    }
  
  light1 = new point_light (vec(3.0, 3.0, 3.0),  vec(20.0, 20.0, 20.0));
  light2 = new point_light (vec(-3.0, 3.0, -3.0), vec( 3.0, 3.0, 10.0));
  
  tracer->lights.push_back  (light1);
  tracer->lights.push_back  (light2);
  
  surf = SDL_CreateRGBSurface (SDL_SWSURFACE,
			       640, 480, 32,
			       0x000000ff,
			       0x0000ff00,
			       0x00ff0000,
			       0xff000000);
  
  // Setup viewing transformations as in OpenGL.
  matrix_4d view = lookat(vec(0.0, 1.0, 6.0),
			  vec(0.0, 0.0, 0.0),
			  vec(0.0, 1.0, 0.0));
  matrix_4d P  = perspective (M_PI/4.0, 640.0/480.0, 0.5, 10000.0);
  matrix_4d VP = viewport<double>(0, 0, 640, 480);
  projection_matrix = VP * P * view;
  projection_view_projection = VP * P;
  view_matrix = view;
}


/// Called when there are no events to handle. 
void idle (void)
{
  if (scanline < surf->h)
    {
      if (use_dof)
	tracer->dof_trace_scanline (view_matrix,
                                    projection_view_projection,
                                    surf, scanline++);
      
      else if (use_path_tracer)
        tracer->path_trace_scanline (projection_matrix, surf, scanline++,
				     false);
      
      else if (use_russian_path_tracer)
        tracer->path_trace_scanline (projection_matrix, surf, scanline++,
				     true);
      
      else
        tracer->trace_scanline (projection_matrix, surf, scanline++);
    }
  render_scene ();
}

void update_scene (void)
{}

/// Shows the raytraced image using OpenGL.
void render_scene     (void)
{
  glClear (GL_COLOR_BUFFER_BIT);
  // This is a bit crappy way to do this. 
  glMatrixMode (GL_PROJECTION);
  glLoadIdentity ();
  glMatrixMode (GL_MODELVIEW);
  glLoadIdentity ();
  glViewport (0, 0, 640, 480);
  glRasterPos2s (-1, -1);
  glDrawPixels (surf->w, surf->h, GL_RGBA, GL_UNSIGNED_BYTE, surf->pixels);
  SDL_GL_SwapBuffers ();
}
